<?php

namespace App\Common\Util\Lock;

use App\Common\Util\TP;

/**
 * Class ChannelLock
 */
class RedisLock implements LockInterface
{
    private readonly string $lockSign;
    private float $startTime;
    private float $waitTime;

    public function __construct(string $lockSign, float $waitTime)
    {
        $appName = TP::config('app.app_key');
        if ($appName) {
            $appName = substr(md5($appName), 0, 5);
        }
        $this->lockSign = $appName . ':' . $lockSign;
        $this->startTime = microtime(true);
        $this->waitTime = $waitTime;
    }


    /**
     * 上锁
     *
     * @param string $lockSign
     * @param int    $ttl
     * @param int    $waitTime
     *
     * @return bool|RedisLock
     * @throws \RedisException
     */
    public static function up(string $lockSign, int $ttl = 5, int $waitTime = 0): bool|static
    {
        $redis     = self::getRedis();
        $redisLock = new self($lockSign, $waitTime);

        $res   = $redis->set($redisLock->lockSign, 1, ['nx', 'ex' => $ttl]);

        if (!$res && $waitTime <= 0) {
            return false;
        }
        $waitTime *= 1000000;
        while (!$res) {
            $microseconds = 10000;
            $waitTime -= $microseconds;
            usleep($microseconds);

            if ($waitTime <= 0) {
                return false;
            }

            $res = $redis->set($redisLock->lockSign, 1, ['nx', 'ex' => $ttl]);
        }

        return $redisLock;
    }

    /**
     * 下锁
     *
     * @return void
     */
    public function down(): void
    {
        try {
            if ($this->waitTime && microtime(true) - $this->startTime > $this->waitTime) {
                return;
            }
            self::getRedis()->del($this->lockSign);
        } catch (\RedisException $e) {
        }
    }

    /**
     * @return object|\Redis
     */
    private static function getRedis(): \Redis
    {
        return TP::cache()->store('redis')->handler();
    }
}
